﻿//////////////////////////////////////////////////////////////////
//
// Network\NetworkClient.cs
//
// Copyright (c) 2012 Dan Pike.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities;

namespace Network
{
   public class NetworkClient : NetworkGui
   {
      /// <summary>
      /// Triggered when the planet goes online/offline
      /// </summary>
      public event PlanetOnlineHandler EventPlanetOnline;

      /// <summary>
      /// The time at which we last received an update for a planet. Used to reduce
      /// the traffic on a reconnection etc
      /// </summary>
      private DateTime lastKnownGood_;

      /// <summary>
      /// The senderId of the server that is running our planet
      /// </summary>
      private string serverId_;

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      public NetworkClient(string senderId, GuiObjects gui)
         : base(senderId, gui, true)
      {
         lastKnownGood_ = DateTime.MinValue;

         // Register to receive notifications from the base class when the
         // heartbeat (and therefore planet) status changes
         this.EventSenderChanged += new SenderChangedHandler(HandleSenderChanged);
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      void HandleSenderChanged(object sender, NetworkGui.SenderChangedEventArgs eventArgs)
      {
         try
         {
            // We are only interested here in the status of our server or, if we have no server,
            // in any server that is running our planet
            if (eventArgs.isClient)
            {
               // It's not a server, so ignore this HB here
               return;
            }
            if (PlanetName == eventArgs.planetName)
            {
               if (string.IsNullOrEmpty(serverId_))
               {
                  // We don't have a server, yet. Will this one do?
                  if (eventArgs.isAlive)
                  {
                     serverId_ = eventArgs.senderId;
                     logger_.DebugFormat("connecting to server '{0}' that is running our planet '{1}'",
                        serverId_, PlanetName);

                     // Send a connect request to ask for updates
                     IConnectPlanet msg = Program.Loader.Create<IConnectPlanet>();
                     msg.PlanetName = PlanetName;
                     msg.LastKnownGood = lastKnownGood_;
                     Transport.Broadcast<IConnectPlanet>(msg);
                  }
               }
               else if ((serverId_ == eventArgs.senderId) && !eventArgs.isAlive)
               {
                  logger_.WarnFormat("lost connection to server '{0}' that is running our planet '{1}'",
                     serverId_, PlanetName);
                  //¬¬¬ Need to handle this - update display etc
               }
            }
            else if (serverId_ == eventArgs.senderId)
            {
               logger_.DebugFormat("server '{0}' is no longer running our planet '{1}'",
                  serverId_, PlanetName);
               //¬¬¬ Need to handle this - update display etc
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      public override void Shutdown()
      {
         if (!string.IsNullOrEmpty(PlanetName))
         {
            // Tell the planet to stop notifying us about this planet
            Transport.ClosePlanet(PlanetName);
         }
         base.Shutdown();
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Periodic processing on the GUI thread
      /// </summary>
      /// <param name="now"></param>
      public override void Heartbeat(DateTime now)
      {
         try
         {
            base.Heartbeat(now);
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="message"></param>
      private bool processPlanetOnline(IPlanetOnline planetOnline)
      {
         try
         {
            if (PlanetName == planetOnline.PlanetName)
            {
               // This is our planet!
               if (null != EventPlanetOnline)
               {
                  PlanetOnlineEventArgs eventArgs2 = new PlanetOnlineEventArgs();
                  eventArgs2.PlanetOnline = planetOnline;
                  EventPlanetOnline(this, eventArgs2);
               }
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// 
      /// </summary>
      /// <param name="message"></param>
      private bool processTileImage(ITileImage tileImage)
      {
         try
         {
            if (PlanetName == tileImage.PlanetName)
            {
               // This is our planet, put the image in the terrain cache
               // so that the renderer can get to it
               Planet.Terrain.TileImage(tileImage.Location, tileImage.Heights);
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// Abstract implementation of NetworkGui.
      /// 
      /// We are on a worker thread.
      /// </summary>
      /// <param name="eventArgs"></param>
      /// <returns></returns>      
      protected override bool HandleReceivedMessage(ReceivedMessageEventArgs eventArgs)
      {
         try
         {
            IPlanetOnline planetOnline;
            if (null != (planetOnline = eventArgs.message as IPlanetOnline))
            {
               return processPlanetOnline(planetOnline);
            }
            ITileImage tileImage;
            if (null != (tileImage = eventArgs.message as ITileImage))
            {
               return processTileImage(tileImage);
            }
         }
         catch (System.Exception ex)
         {
            logger_.Exception(ex);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// We are a client and want to open a connection to a planet
      /// </summary>
      public bool openPlanet(string name)
      {
         // Create the planet
         IPlanetFact planetFact = Program.Almanac.Planets.First(planet => planet.DisplayName == name);
         Planet = Program.Loader.Create<IPlanet>();
         if (Planet.Initialize(planetFact, name))
         {
            // Tell the transport layer to register us with the planet
            return Transport.OpenPlanet(name);
         }
         return false;
      }

      ////////////////////////////////////////////////////////////////////////
      /// <summary>
      /// We are a client and want to open a connection to a planet
      /// </summary>
      public void closePlanet()
      {
         if (!string.IsNullOrEmpty(PlanetName))
         {
            Transport.ClosePlanet(PlanetName);
            Planet = null;
         }
      }
   }
}
